/* -------------------------------------------------------------------
 GeoVISTA Center (Penn State, Dept. of Geography)
 Java source file for the class ProjectionEquidistantConic
 Copyright (c), 2002, GeoVISTA Center
 Licensed under Simplified BSD License
 Original Authors: Frank Hardisty, Fritz Kessler
 $Author: hardisty $
 $Id: ProjectionEquidistantConic.java,v 1.5 2004/05/05 17:23:10 hardisty Exp $
 $Date: 2004/05/05 17:23:10 $
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.
 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.
 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc.,
 -------------------------------------------------------------------   */

package edu.psu.geovista.app.map;

import java.awt.*;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;


/**
 * Equidistant Conic projection, useful for reasonably large sections of
 * the United States, or other contries in similar latitudes.
 */
public class ProjectionEquidistantConic implements Projection {
  private double centralMeridian = Double.NaN;
  private double standardParallelOne = Double.NaN;
  private double standardParallelTwo = Double.NaN;
  private double centralLatitude = Double.NaN;

  /*
   * Coordinates should be passed in as radians.
   * The projected values are returned in radians as well.
   * The second argument can be null, or not. If it is not, that Point2D will
   * returned by the method
   */
  public Point2D.Double project(double lat, double longVal, Point2D.Double pt) {
    if (Double.isNaN(centralLatitude) || Double.isNaN(centralMeridian) ||
          Double.isNaN(standardParallelOne) ||
          Double.isNaN(standardParallelOne)) {
      throw new IllegalStateException(
          "ProjectionEquidistantConic needs to have " +
          "member variables set.");
    }

    if (pt == null) {
      pt = new Point2D.Double();
    }

    this.projectLongitudeLatititudeRadians(longVal, lat, this.centralMeridian,
                                           this.standardParallelOne,
                                           this.standardParallelTwo,
                                           this.centralLatitude, pt);

    //pt.setLocation(longVal, lat);
    return pt;
  }

  /*
   * array[0] = X
   * array[1] = Y
   *
   */
  public static void projectLongitudeLatititudeRadians(double longitudeRadians,
                                                       double latitudeRadians,
                                                       double centralMeridian,
                                                       double standardParallelOne,
                                                       double standardParallelTwo,
                                                       double centralLatitude,
                                                       Point2D.Double pt) {
    //This small check re-adjusts all longitude values depending on the placement of the central meridian.
    if (centralMeridian < 0) {
      //A check to see if each longitude value is west of the central meridian.
      //Thus, those meridians in the eastern hemisphere that are greater than pi distance away from
      //the central meridian are subtracted from 2 * pi. These longitude values are now within -pi to +pi ranage.
      if (Math.abs(longitudeRadians - centralMeridian) > Math.PI) {
        //Using -90 as the central meridian
        //e.g., Abs(100 - (-90)) = 190; which is greater than 180
        longitudeRadians = longitudeRadians - (2 * Math.PI);

        //Thus, (100 - 360) = -260; which is within the +pi to -pi range.
      }
    }

    if (centralMeridian > 0) {
      // A check to see if each longitude value is east of the central meridian.
      // Thus, those meridians in the western hemisphere that are greater than pi distance away from
      // the central meridian are added to 2 * pi. These longitude values are now within -pi to +pi ranage.
      if ((longitudeRadians - centralMeridian) > Math.PI) {
        // Using 90 as the central meridian
        // e.g., Abs(-100 - (90)) = 190; which is greater than 180
        longitudeRadians = longitudeRadians + (2 * Math.PI);

        // Thus, (-100 + 360) = 260; which is within the +pi to -pi range.
      }
    }

    double eQConic_n;
    double eQConic_G;
    double eQConic_P;
    double eQConic_Po;
    double eQConic_Theta;


    // The first step in calculating the Equidistant Conic x and y plotting coordinates
    // requires solving for several intermediate values.
    eQConic_n = (Math.cos(standardParallelOne) -
                Math.cos(standardParallelTwo)) / (standardParallelTwo -
                standardParallelOne);
    eQConic_G = (Math.cos(standardParallelOne) / eQConic_n) +
                standardParallelOne;
    eQConic_Po = 0.65 * (eQConic_G - centralLatitude);
    eQConic_P = 0.65 * (eQConic_G - latitudeRadians);
    eQConic_Theta = eQConic_n * (longitudeRadians - centralMeridian);

    // Defining the Equidistant Conic projection plotting coordinates
    double eQConic_Y = eQConic_Po - (eQConic_P * (Math.cos(eQConic_Theta)));
    double eQConic_X = eQConic_P * Math.sin(eQConic_Theta);


    //double[] xY = new double[2];
    pt.x = eQConic_X;
    pt.y = eQConic_Y;

    //return  pt;
  }

  public void setCentralMeridian(double centralMeridian) {
    this.centralMeridian = centralMeridian;
  }

  public double getCentralMeridian() {
    return this.centralMeridian;
  }

  public void setStandardParallelOne(double standardParallelOne) {
    this.standardParallelOne = standardParallelOne;
  }

  public double getStandardParallelOne() {
    return this.standardParallelOne;
  }

  public void setStandardParallelTwo(double standardParallelTwo) {
    this.standardParallelTwo = standardParallelTwo;
  }

  public double getStandardParallelTwo() {
    return this.standardParallelTwo;
  }

  public void setCentralLatitude(double centralLatitude) {
    this.centralLatitude = centralLatitude;
  }

  public double getCentralLatitude() {
    return this.centralLatitude;
  }

  public Shape transform(Shape shpIn) {
    PathIterator pit = shpIn.getPathIterator(null);
    GeneralPath gep = new GeneralPath(shpIn);

    System.out.println("hi i'm here");

    return shpIn;
  }

  /**
  * DOCUMENT ME!
  *
  * @param s DOCUMENT ME!
  *
  * @return DOCUMENT ME!
  */
  public Shape project(Shape s) {

    GeneralPath p = new GeneralPath();
    float[] coords = { 0, 0, 0, 0, 0, 0 };
    float first_x = 0;
    float first_y = 0;
    float first_tx = 0;
    float first_ty = 0;
    float prev_x = 0;
    float prev_y = 0;
    float prev_tx = 0;
    float prev_ty = 0;
    double x=0;
    double y=0;
    Point2D.Double pt = new Point2D.Double();

    for (PathIterator iter = s.getPathIterator(null);
         !iter.isDone();
         iter.next()) {
      switch (iter.currentSegment(coords)) {
      case PathIterator.SEG_MOVETO:
        first_x = coords[0];
        first_y = coords[1];
        x  = Math.toRadians(first_x);
        y = Math.toRadians(first_y);

        this.project(y,x,pt);

        first_x = (float)pt.getX();
        first_y = (float)pt.getY();
        p.moveTo(first_x,first_y);
        break;

      case PathIterator.SEG_LINETO:
        first_x = coords[0];
        first_y = coords[1];
        x  = Math.toRadians(first_x);
        y = Math.toRadians(first_y);
        this.project(y,x,pt);

        first_x = (float)pt.getX();
        first_y = (float)pt.getY();
        p.lineTo(first_x,first_y);
        break;

      case PathIterator.SEG_QUADTO:
        throw new IllegalArgumentException("Projection doesn't know what to do with this");


      case PathIterator.SEG_CUBICTO:
        throw new IllegalArgumentException("Projection doesn't know what to do with this");

      case PathIterator.SEG_CLOSE:
        p.closePath();
        break;
      }
    }

    return p;
  }
}